<?php

namespace App\Common\Libs\Ali;

use Aliyun\OSS\OSSClient as Client;
use App\Exceptions\Error;

/**
 * Class Oss
 * @package App\Common\Libs
 */
class OssClient
{

    /**
     * oss bucket
     * @var \Illuminate\Config\Repository|mixed
     */
    public $bucket;

    /**
     * oss client
     * @var Client
     */
    public $client;

    /**
     * oss instance
     * @var $instance
     */
    protected static $instance;

    /**
     * retry number
     * @var int
     */
    const RETRY_NUM = 3;

    /**
     * Oss constructor.
     * @param string $accessKeyId
     * @param string $accessKeySecret
     * @param string $endPoint
     * @param string $bucket
     */
    public function __construct($accessKeyId = '', $accessKeySecret = '', $endPoint = '', $bucket = '')
    {
        $accessKeyId = $accessKeyId ? : config('alioss.AccessKeyId');
        $accessKeySecret = $accessKeySecret ? : config('alioss.AccessKeySecret');
        $endPoint = $endPoint ? : config('alioss.ossServer');
        $this->bucket = $bucket ? : config('alioss.BucketName');
        $this->client = Client::factory([
            'Endpoint' => $endPoint,
            'AccessKeyId'  => $accessKeyId,
            'AccessKeySecret' => $accessKeySecret,
        ]);
    }

    /**
     * get instance
     * @return OssClient
     */
    public static function getInstance()
    {
        if (self::$instance instanceof Client) {
            return self::$instance;
        }
        return self::$instance = new self();
    }

    /**
     * 将本地文件上传到 OSS
     * @param $key    string oss文件保存路径
     * @param $file   string 本地文件路径
     * @param string $bucket
     * @param array $options
     * @param bool $isByte
     * @param int $retry
     * @return \Aliyun\OSS\Models\PutObjectResult|mixed
     */
    public function putObject($key, $file, $isByte = false, $bucket = '', $options = [], $retry = self::RETRY_NUM)
    {
        if ($retry <= 1) {
            $result = $this->putObjectWithoutRetry($key, $file, $isByte, $bucket, $options);
        } else {
            $result = $this->retry(self::RETRY_NUM, 'putObjectWithoutRetry', $key, $file, $isByte, $bucket, $options);
        }
        return ['info' => array_values((array)$result), 'url' => $this->getPublicUrl($key)];
    }

    /**
     * 获取文件信息
     * @param $key    string oss文件保存路径
     * @param string $bucket
     * @param int $retry
     * @return \Aliyun\OSS\Models\OSSObject|mixed
     */
    public function getObject($key, $bucket = '', $retry = self::RETRY_NUM)
    {
        try {
            if ($retry <= 1) {
                $result = $this->getObjectWithoutRetry($key, $bucket = '');
            } else {
                $result = $this->retry(self::RETRY_NUM, 'getObjectWithoutRetry', $key, $bucket);
            }
        } catch (\Exception $e) {
            return Error::handle('', $e->getCode(), $e->getMessage(), true);
        }
        return $result;
    }

    /**
     * 获取文件公共 url
     * @param $key
     * @return string
     */
    public function getPublicUrl($key)
    {
        return config('alioss.ossServer').'/'.$key;
    }

    /**
     * get bucket
     * @return \Illuminate\Config\Repository|mixed
     */
    public function getBucket()
    {
        return $this->bucket ? : config('alioss.BucketName');
    }

    /**
     * set bucket
     * @param $bucket
     * @return $this
     */
    public function setBucket($bucket)
    {
        $this->bucket = $bucket;
        return $this;
    }

    /**
     * @param $times
     * @param $method
     * @param $parameters
     * @return mixed
     */
    protected function retry($times, $method, ...$parameters)
    {
        try {
            static $retry = 1;
            try {
                $result = $this->$method(...$parameters);
            } catch (\Exception $e) {
                if ($retry < $times) {
                    $retry ++;
                    return $this->$method(...$parameters);
                }
                throw new \Exception($e->getMessage(), $e->getCode());
            }
        } catch (\Exception $e) {
            $result = $this->$method(...$parameters);
        }
        return $result;
    }

    /**
     * @param $key
     * @param $file
     * @param string $bucket
     * @param array $options
     * @param bool $isByte
     * @return \Aliyun\OSS\Models\PutObjectResult
     */
    protected function putObjectWithoutRetry($key, $file, $isByte = false, $bucket = '', $options = [])
    {
        $oss = self::getInstance();
        $bucket = $bucket ? : $this->getBucket();
        if ($isByte) {// 文件流
            $content = '';
            // 这里就是input中类型是file名字是file
            $fp = fopen($file, 'r');
            if ($fp) {
                while (!feof($fp)) {
                    $content .= fgets($fp, 8888);
                }
            }
            $handle = $content;
        } else {// 普通文件
            $fp = $handle = fopen($file, 'r');
        }
        $value  = $oss->client->putObject(array_merge([
            'Bucket'        => $bucket,
            'Key'           => $key,
            'Content'       => $handle,
            'ContentLength' => filesize($file),
        ], $options));
        fclose($fp);
        return $value;
    }

    /**
     * @param $key
     * @param string $bucket
     * @return \Aliyun\OSS\Models\OSSObject
     */
    protected function getObjectWithoutRetry($key, $bucket = '')
    {
        $oss = self::getInstance();
        $bucket = $bucket ? : $this->getBucket();
        return $oss->client->getObject([
            'Bucket' => $bucket,
            'Key' => $key,
        ]);
    }

}
